﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Skugo.Shared;

namespace Skugo
{
    public class LocalPropertyNode
    {
        /// <summary>
        /// The type of the node (tells us how to interpret its data).
        /// </summary>
        public NodeKind Kind;

        /// <summary>
        /// The type that the node aims to represent, for example, a value
        /// type like Int32, or an object type like a List or Composition.
        /// </summary>
        public Type Type;

        /// <summary>
        /// Whether or not this node is by default expanded. Only applicable when the NodeKind is Expandable.
        /// </summary>
        public Boolean DefaultExpanded;

        /// <summary>
        /// The parent node (or null if this is the root node). The parent node is needed because when
        /// sending a set or call over to the local rpc, the packet encodes the member index, however,
        /// that does not direclty map to an existing member owner. We have to walk down the chain using the
        /// getter for each child until we reach the bottom, which we use the setter or call the method.
        /// </summary>
        public LocalPropertyNode Parent;

        /// <summary>
        /// The name of the property if it has one, otherwise the property is considered an unnamed property.
        /// </summary>
        public String OptionalName;

        /// <summary>
        /// An optional description for the given object.
        /// </summary>
        public String OptionalDescription;

        /// <summary>
        /// The category that this property belongs to (optional, can be null).
        /// </summary>
        public String OptionalCategory;

        /// <summary>
        /// The array of all children that sit below this node.
        /// </summary>
        public List<LocalPropertyNode> Children = new List<LocalPropertyNode>();

        /// <summary>
        /// A function we query to see if values can be added to this property.
        /// Typically used for things like compositions or containers.
        /// </summary>
        public EnumerateChildTypesFn EnumerateChildTypes;

        /// <summary>
        /// Adds a child to the property. Used for containers.
        /// This function takes the index that was given by 'EnumerateChildTypes', or -1 if
        /// the enumerate function was not provided.
        /// </summary>
        public AddChildFn AddChild;

        /// <summary>
        /// Removes a child from the property grid by index. The index cooresponds to
        /// the child at the given index in the list of 'Children'.
        /// </summary>
        public RemoveChildFn Remove;

        /// <summary>
        /// If the method is set, then the property node is assumed to represent a method, and not a value
        /// or an object of any kind.
        /// </summary>
        public MethodInfo Method;

        /// <summary>
        /// Get the property's value from a given owner.
        /// </summary>
        public GetPropertyFn Getter;

        /// <summary>
        /// Set the property's value for a given owner.
        /// </summary>
        public SetPropertyFn Setter;

        /// <summary>
        /// All the valid options for this node. Only valid if the if the node kind is an enumerable.
        /// </summary>
        public String[] EnumeratedOptions;

        /// <summary>
        /// Compare one node to another.
        /// </summary>
        /// <param name="node">The node we're comparing against.</param>
        /// <returns>True if the nodes are the same, false otherwise.</returns>
        public Boolean Equals(LocalPropertyNode node)
        {
            // If the two nodes represent fundamentally different types...
            if (this.Kind != node.Kind)
                return false;

            // If the types on the nodes are different, then they are different
            if (this.Type != node.Type)
                return false;

            // If they aren't both methods or both values/objects, then they are different
            if (this.Method != node.Method)
                return false;

            // Both nodes must have the same number of children
            // Note that we don't actually need to compare the children, because that will
            // happen automatically when comparing the flattened hierarchy
            if (this.Children.Count != node.Children.Count)
                return false;

            // If they have different names (or one has a name and one doesn't)
            // then we consider the nodes to be different
            if (this.OptionalName != node.OptionalName)
                return false;

            // If they have different descriptions...
            if (this.OptionalDescription != node.OptionalDescription)
                return false;

            // If they have different categories...
            if (this.OptionalCategory != node.OptionalCategory)
                return false;

            // If the enumerated options are different at all (null, different arrays, etc)
            if (this.EnumeratedOptions != node.EnumeratedOptions)
            {
                // If they are both valid...
                if (this.EnumeratedOptions != null & node.EnumeratedOptions != null)
                {
                    // If they have a different number of enumerated options...
                    if (this.EnumeratedOptions.Length != node.EnumeratedOptions.Length)
                        return false;

                    // Otherwise, we need to loop through and compare all options
                    for (Int32 i = 0; i < this.EnumeratedOptions.Length; ++i)
                    {
                        // If any of the options are different, then return that it's not the same
                        if (this.EnumeratedOptions[i] != node.EnumeratedOptions[i])
                            return false;
                    }
                }
                else
                {
                    // One enumeration was null, and the other was not
                    return false;
                }
                
                // Otherwise, the options are either both null, or are the exact same arrays
            }

            // If the type we represent is an enumerable, we need to also compare the options
            if (this.Kind == NodeKind.Enumerable)
            {
                // Get the type information about the enumerable type
                var typeInfo = TypeFactory.GetInfoFromEnumerableType(this.Type);

                typeInfo.Enumerate();
            }

            // If we got all the way here, then we must be the same
            return true;
        }
    }
}
